أتقن استخدام React.createRef للتلاعب الإلزامي بـ DOM ونماذج المكونات. تعلم متى وكيفية استخدامه بفعالية في مكونات الصنف للتحكم في التركيز والوسائط والتكامل مع المكتبات الخارجية.
React createRef: الدليل الشامل للتفاعلات المباشرة مع المكونات وعناصر DOM
في المشهد الواسع والمعقد لتطوير الويب الحديث، برز React كقوة مهيمنة، يُحتفى به بشكل أساسي لنهجه التقريري في بناء واجهات المستخدم. يشجع هذا النموذج المطورين على وصف ما يجب أن تبدو عليه واجهة المستخدم الخاصة بهم بناءً على البيانات، بدلاً من تحديد كيفية تحقيق تلك الحالة المرئية من خلال التلاعب المباشر بـ DOM. وقد بسّط هذا التجريد تطوير واجهة المستخدم بشكل كبير، مما جعل التطبيقات أكثر قابلية للتنبؤ، وأسهل في الفهم، وذات أداء عالٍ.
ومع ذلك، نادرًا ما يكون عالم تطبيقات الويب الحقيقي تقريريًا بالكامل. هناك سيناريوهات محددة، ولكنها شائعة، حيث يصبح التفاعل المباشر مع عنصر DOM (نموذج كائن المستند) الأساسي أو نسخة مكون صنف ليس فقط مناسبًا، بل ضروريًا تمامًا. تُعرف هذه "المخارج" من تدفق React التقريري باسم المراجع (refs). ومن بين الآليات المختلفة التي يقدمها React لإنشاء وإدارة هذه المراجع، تبرز React.createRef() كواجهة برمجة تطبيقات أساسية، ذات صلة خاصة للمطورين الذين يعملون مع المكونات الصنفية.
يهدف هذا الدليل الشامل إلى أن يكون مرجعك النهائي لفهم وتطبيق وإتقان React.createRef(). سننطلق في استكشاف مفصل لغرضه، ونتعمق في بنيته وتطبيقاته العملية، ونسلط الضوء على أفضل ممارساته، ونميزه عن استراتيجيات إدارة المراجع الأخرى. سواء كنت مطور React متمرسًا تسعى لترسيخ فهمك للتفاعلات الأمرية أو وافدًا جديدًا يسعى لاستيعاب هذا المفهوم الحاسم، فإن هذا المقال سيزودك بالمعرفة اللازمة لبناء تطبيقات React أكثر قوة وأداءً وإتاحة عالميًا، تتعامل بأناقة مع المتطلبات المعقدة لتجارب المستخدم الحديثة.
فهم المراجع (Refs) في React: جسر بين العالمين التقريري والأمري
في جوهره، يدعم React أسلوب البرمجة التقريري. أنت تحدد مكوناتك، وحالتها، وكيفية عرضها. ثم يتولى React المهمة، محدثًا بفعالية DOM الفعلي للمتصفح ليعكس واجهة المستخدم التي أعلنت عنها. هذه الطبقة من التجريد قوية للغاية، وتحمي المطورين من تعقيدات ومخاطر الأداء للتلاعب المباشر بـ DOM. ولهذا السبب غالبًا ما تبدو تطبيقات React سلسة وسريعة الاستجابة.
تدفق البيانات أحادي الاتجاه وحدوده
تكمن قوة React المعمارية في تدفق البيانات أحادي الاتجاه. تتدفق البيانات بشكل متوقع من المكونات الأصل إلى الأبناء عبر الخصائص (props)، وتؤدي تغييرات الحالة داخل المكون إلى إعادة العرض التي تنتشر عبر شجرته الفرعية. يعزز هذا النموذج القدرة على التنبؤ ويجعل تصحيح الأخطاء أسهل بكثير، حيث تعرف دائمًا مصدر البيانات وكيف تؤثر على واجهة المستخدم. ومع ذلك، لا تتماشى كل التفاعلات تمامًا مع هذا التدفق للبيانات من الأعلى إلى الأسفل.
لنأخذ في الاعتبار سيناريوهات مثل:
- التركيز برمجيًا على حقل إدخال عندما ينتقل المستخدم إلى نموذج.
- تشغيل أساليب
play()أوpause()على عنصر<video>. - قياس أبعاد البكسل الدقيقة لعنصر
<div>معروض لتعديل التخطيط ديناميكيًا. - دمج مكتبة جافاسكريبت معقدة من طرف ثالث (مثل مكتبة الرسوم البيانية D3.js أو أداة تصور الخرائط) التي تتوقع الوصول المباشر إلى حاوية DOM.
هذه الإجراءات هي في جوهرها أمرية – فهي تتضمن إصدار أمر مباشر لعنصر للقيام بشيء ما، بدلاً من مجرد الإعلان عن حالته المرغوبة. بينما يمكن لنموذج React التقريري غالبًا تجريد العديد من التفاصيل الأمرية، فإنه لا يلغي الحاجة إليها تمامًا. وهنا يأتي دور المراجع بالضبط، حيث توفر مخرجًا متحكمًا فيه لأداء هذه التفاعلات المباشرة.
متى تستخدم المراجع: التنقل بين التفاعلات الأمرية والتقريرية
المبدأ الأكثر أهمية عند العمل مع المراجع هو استخدامها باعتدال وفقط عند الضرورة القصوى. إذا كان يمكن إنجاز مهمة ما باستخدام آليات React التقريرية القياسية (الحالة والخصائص)، فيجب أن يكون هذا دائمًا نهجك المفضل. يمكن أن يؤدي الاعتماد المفرط على المراجع إلى كود يصعب فهمه وصيانته وتصحيح أخطائه، مما يقوض الفوائد ذاتها التي يوفرها React.
ومع ذلك، بالنسبة للحالات التي تتطلب حقًا الوصول المباشر إلى عقدة DOM أو نسخة مكون، فإن المراجع هي الحل الصحيح والمقصود. إليك تفصيل أكثر لحالات الاستخدام المناسبة:
- إدارة التركيز، واختيار النص، وتشغيل الوسائط: هذه أمثلة كلاسيكية حيث تحتاج إلى التفاعل الأمري مع العناصر. فكر في التركيز التلقائي على شريط البحث عند تحميل الصفحة، أو تحديد كل النص في حقل إدخال، أو التحكم في تشغيل مشغل صوت أو فيديو. عادةً ما يتم تشغيل هذه الإجراءات بواسطة أحداث المستخدم أو أساليب دورة حياة المكون، وليس بمجرد تغيير الخصائص أو الحالة.
- تشغيل الرسوم المتحركة الأمرية: بينما يمكن التعامل مع العديد من الرسوم المتحركة بشكل تقريري باستخدام انتقالات/رسوم CSS أو مكتبات الرسوم المتحركة في React، فإن بعض الرسوم المتحركة المعقدة عالية الأداء، خاصة تلك التي تتضمن واجهة برمجة تطبيقات HTML Canvas أو WebGL، أو التي تتطلب تحكمًا دقيقًا في خصائص العنصر التي من الأفضل إدارتها خارج دورة عرض React، قد تتطلب استخدام المراجع.
- التكامل مع مكتبات DOM من طرف ثالث: تم تصميم العديد من مكتبات جافاسكريبت العريقة (مثل D3.js، و Leaflet للخرائط، ومختلف أدوات واجهة المستخدم القديمة) للتلاعب المباشر بعناصر DOM محددة. توفر المراجع الجسر الأساسي، مما يسمح لـ React بعرض عنصر حاوية، ثم منح المكتبة الخارجية الوصول إلى تلك الحاوية لمنطق العرض الأمري الخاص بها.
-
قياس أبعاد العنصر أو موضعه: لتنفيذ تخطيطات متقدمة، أو العرض الافتراضي، أو سلوكيات التمرير المخصصة، غالبًا ما تحتاج إلى معلومات دقيقة حول حجم العنصر، أو موضعه بالنسبة لمنطقة العرض، أو ارتفاع التمرير الخاص به. واجهات برمجة التطبيقات مثل
getBoundingClientRect()لا يمكن الوصول إليها إلا على عقد DOM الفعلية، مما يجعل المراجع لا غنى عنها لمثل هذه الحسابات.
على العكس من ذلك، يجب عليك تجنب استخدام المراجع للمهام التي يمكن تحقيقها بشكل تقريري. وهذا يشمل:
- تعديل نمط المكون (استخدم الحالة للتنسيق الشرطي).
- تغيير المحتوى النصي لعنصر (مرره كخاصية أو قم بتحديث الحالة).
- التواصل المعقد بين المكونات (الخصائص والاستدعاءات المرتجعة أفضل بشكل عام).
- أي سيناريو تحاول فيه تكرار وظائف إدارة الحالة.
التعمق في React.createRef(): النهج الحديث للمكونات الصنفية
تم تقديم React.createRef() في React 16.3، مما يوفر طريقة أكثر وضوحًا ونظافة لإدارة المراجع مقارنة بالطرق القديمة مثل المراجع النصية (المهملة الآن) والمراجع المرتجعة (لا تزال صالحة ولكنها غالبًا ما تكون أكثر تفصيلاً). تم تصميمه ليكون آلية إنشاء المراجع الأساسية للمكونات الصنفية، حيث يقدم واجهة برمجة تطبيقات كائنية التوجه تتناسب بشكل طبيعي مع بنية الصنف.
البنية والاستخدام الأساسي: عملية من ثلاث خطوات
إن سير العمل لاستخدام createRef() مباشر ويتضمن ثلاث خطوات رئيسية:
-
إنشاء كائن مرجع: في مُنشئ المكون الصنفي الخاص بك، قم بتهيئة نسخة مرجع عن طريق استدعاء
React.createRef()وتعيين قيمته المرتجعة إلى خاصية نسخة (على سبيل المثال،this.myRef). -
إرفاق المرجع: في أسلوب
renderالخاص بمكونك، قم بتمرير كائن المرجع الذي تم إنشاؤه إلى سمةrefلعنصر React (سواء كان عنصر HTML أو مكونًا صنفيًا) الذي ترغب في الإشارة إليه. -
الوصول إلى الهدف: بمجرد تركيب المكون، ستكون عقدة DOM أو نسخة المكون المشار إليها متاحة عبر خاصية
.currentلكائن المرجع الخاص بك (على سبيل المثال،this.myRef.current).
import React from 'react';
class FocusInputOnMount extends React.Component {
constructor(props) {
super(props);
this.inputElementRef = React.createRef(); // الخطوة 1: إنشاء كائن مرجع في المُنشئ
console.log('Constructor: Ref current value is initially:', this.inputElementRef.current); // null
}
componentDidMount() {
if (this.inputElementRef.current) {
this.inputElementRef.current.focus();
console.log('ComponentDidMount: Input focused. Current value:', this.inputElementRef.current.value);
}
}
handleButtonClick = () => {
if (this.inputElementRef.current) {
alert(`Input value: ${this.inputElementRef.current.value}`);
}
};
render() {
console.log('Render: Ref current value is:', this.inputElementRef.current); // لا يزال null عند العرض الأولي
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h3>حقل إدخال بتركيز تلقائي</h3>
<label htmlFor="focusInput">أدخل اسمك:</label><br />
<input
id="focusInput"
type="text"
ref={this.inputElementRef} // الخطوة 2: إرفاق المرجع بعنصر <input>
placeholder="اسمك هنا..."
style={{ margin: '10px 0', padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
/><br />
<button
onClick={this.handleButtonClick}
style={{ padding: '10px 15px', background: '#007bff', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
عرض قيمة الإدخال
</button>
<p><em>سيحصل هذا الإدخال تلقائيًا على التركيز عند تحميل المكون.</em></p>
</div>
);
}
}
في هذا المثال، this.inputElementRef هو كائن ستديره React داخليًا. عندما يتم عرض عنصر <input> وتركيبه في DOM، تقوم React بتعيين عقدة DOM الفعلية تلك إلى this.inputElementRef.current. يعتبر أسلوب دورة الحياة componentDidMount هو المكان المثالي للتفاعل مع المراجع لأنه يضمن أن المكون وأبناءه قد تم عرضهم في DOM وأن خاصية .current متاحة ومملوءة.
إرفاق مرجع بعنصر DOM: الوصول المباشر إلى DOM
عندما ترفق مرجعًا بعنصر HTML قياسي (مثل <div>، <p>، <button>، <img>)، ستحتوي خاصية .current لكائن المرجع الخاص بك على عنصر DOM الفعلي الأساسي. يمنحك هذا وصولاً غير مقيد إلى جميع واجهات برمجة تطبيقات DOM القياسية للمتصفح، مما يسمح لك بأداء إجراءات تقع عادةً خارج سيطرة React التقريرية. وهذا مفيد بشكل خاص للتطبيقات العالمية حيث قد يكون التخطيط الدقيق أو التمرير أو إدارة التركيز أمرًا بالغ الأهمية عبر بيئات المستخدم وأنواع الأجهزة المتنوعة.
import React from 'react';
class ScrollToElementExample extends React.Component {
constructor(props) {
super(props);
this.targetDivRef = React.createRef();
this.state = { showScrollButton: false };
}
componentDidMount() {
// إظهار زر التمرير فقط إذا كان هناك محتوى كافٍ للتمرير
// يضمن هذا الفحص أيضًا أن المرجع حالي بالفعل.
if (this.targetDivRef.current && window.innerHeight < document.body.scrollHeight) {
this.setState({ showScrollButton: true });
}
}
handleScrollToTarget = () => {
if (this.targetDivRef.current) {
// استخدام scrollIntoView للتمرير السلس، المدعوم على نطاق واسع عبر المتصفحات عالميًا.
this.targetDivRef.current.scrollIntoView({
behavior: 'smooth', // يجعل التمرير متحركًا لتجربة مستخدم أفضل
block: 'start' // يحاذي الجزء العلوي من العنصر مع الجزء العلوي من منفذ العرض
});
console.log('Scrolled to target div!');
} else {
console.warn('Target div not yet available for scrolling.');
}
};
render() {
return (
<div style={{ padding: '15px' }}>
<h2>التمرير إلى عنصر محدد باستخدام Ref</h2>
<p>يوضح هذا المثال كيفية التمرير برمجيًا إلى عنصر DOM خارج الشاشة.</p>
{this.state.showScrollButton && (
<button
onClick={this.handleScrollToTarget}
style={{ marginBottom: '20px', padding: '10px 20px', background: '#28a745', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
التمرير لأسفل إلى المنطقة المستهدفة
</button>
)}
<div style={{ height: '1500px', background: '#f8f9fa', padding: '20px', marginBottom: '20px', border: '1px dashed #6c757d' }}>
<p>محتوى نائب لإنشاء مساحة تمرير عمودية.</p>
<p>تخيل مقالات طويلة، أو نماذج معقدة، أو لوحات معلومات مفصلة تتطلب من المستخدمين التنقل في محتوى واسع. يضمن التمرير البرمجي أن يتمكن المستخدمون من الوصول بسرعة إلى الأقسام ذات الصلة دون جهد يدوي، مما يحسن إمكانية الوصول وتدفق المستخدم عبر جميع الأجهزة وأحجام الشاشات.</p>
<p>هذه التقنية مفيدة بشكل خاص في النماذج متعددة الصفحات، أو المعالجات خطوة بخطوة، أو التطبيقات أحادية الصفحة ذات التنقل العميق.</p>
</div>
<div
ref={this.targetDivRef} // إرفاق المرجع هنا
style={{
minHeight: '300px',
background: '#e9ecef',
padding: '30px',
border: '2px solid #007bff',
borderRadius: '10px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center'
}}
>
<h3>لقد وصلت إلى المنطقة المستهدفة!</h3>
<p>هذا هو القسم الذي مررنا إليه برمجيًا.</p>
<p>القدرة على التحكم الدقيق في سلوك التمرير أمر حاسم لتعزيز تجربة المستخدم، لا سيما على الأجهزة المحمولة حيث تكون مساحة الشاشة محدودة والتنقل الدقيق أمرًا بالغ الأهمية.</p>
</div>
</div>
);
}
}
يوضح هذا المثال بشكل جميل كيف يوفر createRef التحكم في التفاعلات على مستوى المتصفح. تعتبر إمكانيات التمرير البرمجية هذه حاسمة في العديد من التطبيقات، بدءًا من التنقل في الوثائق الطويلة إلى توجيه المستخدمين عبر مهام سير العمل المعقدة. يضمن الخيار behavior: 'smooth' في scrollIntoView انتقالًا متحركًا لطيفًا، مما يعزز تجربة المستخدم عالميًا.
إرفاق مرجع بمكون صنف: التفاعل مع النسخ
بالإضافة إلى عناصر DOM الأصلية، يمكنك أيضًا إرفاق مرجع بنسخة من مكون صنف. عند القيام بذلك، ستحتوي خاصية .current لكائن المرجع الخاص بك على المكون الصنفي الفعلي المنشأ نفسه. يسمح هذا لمكون أصلي باستدعاء الأساليب المحددة داخل المكون الصنفي الابن مباشرة أو الوصول إلى خصائص نسخته. على الرغم من قوتها، يجب استخدام هذه الإمكانية بحذر شديد، لأنها تسمح بكسر تدفق البيانات أحادي الاتجاه التقليدي، مما قد يؤدي إلى سلوك تطبيق أقل قابلية للتنبؤ.
import React from 'react';
// المكون الصنفي الابن
class DialogBox extends React.Component {
constructor(props) {
super(props);
this.state = { isOpen: false, message: '' };
}
// أسلوب معروض للأصل عبر المرجع
open(message) {
this.setState({ isOpen: true, message });
}
close = () => {
this.setState({ isOpen: false, message: '' });
};
render() {
if (!this.state.isOpen) return null;
return (
<div style={{
position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
padding: '25px 35px', background: 'white', border: '1px solid #ddd', borderRadius: '8px',
boxShadow: '0 5px 15px rgba(0,0,0,0.2)', zIndex: 1000, maxWidth: '400px', width: '90%', textAlign: 'center'
}}>
<h4>رسالة من الأصل</h4>
<p>{this.state.message}</p>
<button
onClick={this.close}
style={{ marginTop: '15px', padding: '8px 15px', background: '#dc3545', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
إغلاق
</button>
</div>
);
}
}
// المكون الصنفي الأصل
class AppWithDialog extends React.Component {
constructor(props) {
super(props);
this.dialogRef = React.createRef();
}
handleOpenDialog = () => {
if (this.dialogRef.current) {
// الوصول إلى نسخة المكون الابن واستدعاء أسلوب 'open' الخاص به
this.dialogRef.current.open('مرحبًا من المكون الأصل! تم فتح هذا الحوار بشكل أمري.');
}
};
render() {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>التواصل بين الأصل والابن عبر المرجع</h2>
<p>يوضح هذا كيف يمكن لمكون أصلي التحكم بشكل أمري في أسلوب مكونه الصنفي الابن.</p>
<button
onClick={this.handleOpenDialog}
style={{ padding: '12px 25px', background: '#007bff', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '1.1em' }}
>
فتح الحوار الأمري
</button>
<DialogBox ref={this.dialogRef} /> // إرفاق المرجع بنسخة مكون صنف
</div>
);
}
}
هنا، يمكن لـ AppWithDialog استدعاء أسلوب open لمكون DialogBox مباشرة عبر مرجعه. يمكن أن يكون هذا النمط مفيدًا لتشغيل إجراءات مثل إظهار نافذة منبثقة، أو إعادة تعيين نموذج، أو التحكم برمجيًا في عناصر واجهة المستخدم الخارجية المغلفة داخل مكون ابن. ومع ذلك، يوصى عمومًا بتفضيل التواصل القائم على الخصائص لمعظم السيناريوهات، وتمرير البيانات والاستدعاءات المرتجعة من الأصل إلى الابن للحفاظ على تدفق بيانات واضح ويمكن التنبؤ به. لا تلجأ إلى المراجع لأساليب المكونات الابن إلا عندما تكون تلك الإجراءات أمرية حقًا ولا تتناسب مع تدفق الخصائص/الحالة النموذجي.
إرفاق مرجع بمكون وظيفي (تمييز حاسم)
من المفاهيم الخاطئة الشائعة، ونقطة تمييز مهمة، أنه لا يمكنك إرفاق مرجع مباشرة باستخدام createRef() بمكون وظيفي. المكونات الوظيفية، بطبيعتها، ليس لها نسخ بنفس طريقة المكونات الصنفية. إذا حاولت تعيين مرجع مباشرة إلى مكون وظيفي (على سبيل المثال، <MyFunctionalComponent ref={this.myRef} />)، فسيصدر React تحذيرًا في وضع التطوير لأنه لا توجد نسخة مكون لتعيينها إلى .current.
إذا كان هدفك هو تمكين مكون أصلي (قد يكون مكونًا صنفيًا يستخدم createRef، أو مكونًا وظيفيًا يستخدم useRef) من الوصول إلى عنصر DOM معروض داخل مكون وظيفي ابن، فيجب عليك استخدام React.forwardRef. يسمح هذا المكون عالي الرتبة للمكونات الوظيفية بكشف مرجع لعقدة DOM محددة أو مقبض أمري بداخلها.
بدلاً من ذلك، إذا كنت تعمل داخل مكون وظيفي وتحتاج إلى إنشاء وإدارة مرجع، فإن الآلية المناسبة هي خطاف useRef، والذي سيتم مناقشته بإيجاز في قسم مقارنة لاحق. من الضروري أن تتذكر أن createRef مرتبط بشكل أساسي بالمكونات الصنفية وطبيعتها القائمة على النسخ.
الوصول إلى عقدة DOM أو نسخة المكون: شرح خاصية `.current`
يدور جوهر تفاعل المراجع حول خاصية .current لكائن المرجع الذي تم إنشاؤه بواسطة React.createRef(). يعد فهم دورة حياته وما يمكن أن يحتويه أمرًا بالغ الأهمية لإدارة المراجع بشكل فعال.
خاصية `.current`: بوابتك إلى التحكم الأمري
خاصية .current هي كائن قابل للتغيير تديره React. وهي بمثابة الرابط المباشر للعنصر أو نسخة المكون المشار إليه. تتغير قيمتها طوال دورة حياة المكون:
-
التهيئة: عند استدعاء
React.createRef()لأول مرة في المُنشئ، يتم إنشاء كائن المرجع، ويتم تهيئة خاصية.currentالخاصة به إلىnull. هذا لأنه في هذه المرحلة، لم يتم عرض المكون بعد، ولا يوجد عنصر DOM أو نسخة مكون ليشير إليها المرجع. -
التركيب: بمجرد عرض المكون في DOM وإنشاء العنصر الذي يحتوي على سمة
ref، تقوم React بتعيين عقدة DOM الفعلية أو نسخة المكون الصنفي إلى خاصية.currentلكائن المرجع الخاص بك. يحدث هذا عادةً مباشرة بعد اكتمال أسلوبrenderوقبل استدعاءcomponentDidMount. لذلك، يعدcomponentDidMountالمكان الأكثر أمانًا وشيوعًا للوصول والتفاعل مع.current. -
إلغاء التركيب: عند إلغاء تركيب المكون من DOM، تقوم React تلقائيًا بإعادة تعيين خاصية
.currentإلىnull. هذا أمر حاسم لمنع تسرب الذاكرة وضمان عدم احتفاظ تطبيقك بمراجع لعناصر لم تعد موجودة في DOM. -
التحديث: في حالات نادرة حيث يتم تغيير سمة
refعلى عنصر أثناء التحديث، سيتم تعيين خاصيةcurrentللمرجع القديم إلىnullقبل تعيين خاصيةcurrentللمرجع الجديد. هذا السلوك أقل شيوعًا ولكنه مهم لملاحظته لتعيينات المراجع الديناميكية المعقدة.
import React from 'react';
class RefLifecycleLogger extends React.Component {
constructor(props) {
super(props);
this.myDivRef = React.createRef();
console.log('1. Constructor: this.myDivRef.current is', this.myDivRef.current); // null
}
componentDidMount() {
console.log('3. componentDidMount: this.myDivRef.current is', this.myDivRef.current); // عنصر DOM الفعلي
if (this.myDivRef.current) {
this.myDivRef.current.style.backgroundColor = '#d4edda'; // تنسيق أمري للعرض
this.myDivRef.current.innerText += ' - المرجع نشط!';
}
}
componentDidUpdate(prevProps, prevState) {
console.log('4. componentDidUpdate: this.myDivRef.current is', this.myDivRef.current); // عنصر DOM الفعلي (بعد التحديثات)
}
componentWillUnmount() {
console.log('5. componentWillUnmount: this.myDivRef.current is', this.myDivRef.current); // عنصر DOM الفعلي (قبل أن يصبح null)
// في هذه المرحلة، قد تقوم بالتنظيف إذا لزم الأمر
}
render() {
// في العرض الأولي، لا يزال this.myDivRef.current هو null لأن DOM لم يتم إنشاؤه بعد.
// في العروض اللاحقة (بعد التركيب)، سيحتوي على العنصر.
console.log('2. Render: this.myDivRef.current is', this.myDivRef.current);
return (
<div
ref={this.myDivRef}
style={{ padding: '20px', border: '1px solid #28a745', margin: '20px', minHeight: '80px', display: 'flex', alignItems: 'center' }}
>
<p>هذا هو div الذي تم إرفاق مرجع به.</p>
</div>
);
}
}
توفر ملاحظة مخرجات وحدة التحكم لـ RefLifecycleLogger رؤية واضحة حول متى يصبح this.myDivRef.current متاحًا. من الضروري دائمًا التحقق مما إذا كان this.myDivRef.current ليس null قبل محاولة التفاعل معه، خاصة في الأساليب التي قد تعمل قبل التركيب أو بعد إلغاء التركيب.
ماذا يمكن أن يحمل `.current`؟ استكشاف محتويات مرجعك
يعتمد نوع القيمة التي يحملها current على ما ترفق به المرجع:
-
عند إرفاقه بعنصر HTML (مثل
<div>،<input>): ستحتوي خاصية.currentعلى عنصر DOM الفعلي الأساسي. هذا كائن جافاسكريبت أصلي، يوفر الوصول إلى مجموعته الكاملة من واجهات برمجة تطبيقات DOM. على سبيل المثال، إذا قمت بإرفاق مرجع بـ<input type="text">، فسيكون.currentكائنHTMLInputElement، مما يسمح لك باستدعاء أساليب مثل.focus()، وقراءة خصائص مثل.value، أو تعديل سمات مثل.placeholder. هذه هي حالة الاستخدام الأكثر شيوعًا للمراجع.this.inputRef.current.focus();
this.videoRef.current.play();
const { width, height } = this.divRef.current.getBoundingClientRect(); -
عند إرفاقه بمكون صنف (مثل
<MyClassComponent />): ستحتوي خاصية.currentعلى نسخة من ذلك المكون الصنفي. هذا يعني أنه يمكنك استدعاء الأساليب المحددة داخل ذلك المكون الابن مباشرة (على سبيل المثال،childRef.current.someMethod()) أو حتى الوصول إلى حالته أو خصائصه (على الرغم من أن الوصول إلى الحالة/الخصائص مباشرة من ابن عبر مرجع لا يشجع عليه عمومًا لصالح الخصائص وتحديثات الحالة). هذه الإمكانية قوية لتشغيل سلوكيات محددة في المكونات الابن التي لا تتناسب مع نموذج التفاعل القائم على الخصائص القياسي.this.childComponentRef.current.resetForm();
// نادرًا، ولكنه ممكن: console.log(this.childComponentRef.current.state.someValue); -
عند إرفاقه بمكون وظيفي (عبر
forwardRef): كما لوحظ سابقًا، لا يمكن إرفاق المراجع مباشرة بالمكونات الوظيفية. ومع ذلك، إذا تم تغليف مكون وظيفي بـReact.forwardRef، فستحتوي خاصية.currentعلى أي قيمة يكشفها المكون الوظيفي صراحةً عبر المرجع المعاد توجيهه. عادة ما يكون هذا عنصر DOM داخل المكون الوظيفي، أو كائن يحتوي على أساليب أمرية (باستخدام خطافuseImperativeHandleبالاقتران معforwardRef).// في الأصل، سيكون myForwardedRef.current هو عقدة DOM أو الكائن المكشوف
this.myForwardedRef.current.focus();
this.myForwardedRef.current.customResetMethod();
حالات الاستخدام العملية لـ `createRef` قيد التنفيذ
لفهم فائدة React.createRef() حقًا، دعنا نستكشف سيناريوهات أكثر تفصيلاً وذات صلة عالمية حيث يثبت أنه لا غنى عنه، متجاوزين إدارة التركيز البسيطة.
1. إدارة التركيز، اختيار النص، أو تشغيل الوسائط عبر الثقافات
هذه أمثلة رئيسية على تفاعلات واجهة المستخدم الأمرية. تخيل نموذجًا متعدد الخطوات مصممًا لجمهور عالمي. بعد أن يكمل المستخدم قسمًا واحدًا، قد ترغب في تحويل التركيز تلقائيًا إلى الإدخال الأول في القسم التالي، بغض النظر عن اللغة أو اتجاه النص الافتراضي (من اليسار إلى اليمين أو من اليمين إلى اليسار). توفر المراجع التحكم اللازم.
import React from 'react';
class DynamicFocusForm extends React.Component {
constructor(props) {
super(props);
this.firstNameRef = React.createRef();
this.lastNameRef = React.createRef();
this.emailRef = React.createRef();
this.state = { currentStep: 1 };
}
componentDidMount() {
// التركيز على الإدخال الأول عند تركيب المكون
this.firstNameRef.current.focus();
}
handleNextStep = (nextRef) => {
this.setState(prevState => ({ currentStep: prevState.currentStep + 1 }), () => {
// بعد تحديثات الحالة وإعادة عرض المكون، يتم التركيز على الإدخال التالي
if (nextRef.current) {
nextRef.current.focus();
}
});
};
render() {
const { currentStep } = this.state;
const formSectionStyle = { border: '1px solid #0056b3', padding: '20px', margin: '15px 0', borderRadius: '8px', background: '#e7f0fa' };
const inputStyle = { width: '100%', padding: '10px', margin: '8px 0', border: '1px solid #ccc', borderRadius: '4px' };
const buttonStyle = { padding: '10px 20px', background: '#007bff', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', marginTop: '10px' };
return (
<div style={{ maxWidth: '600px', margin: '30px auto', padding: '25px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', borderRadius: '10px', background: 'white' }}>
<h2>نموذج متعدد الخطوات مع تركيز مُدار بالمرجع</h2>
<p>الخطوة الحالية: <strong>{currentStep}</strong></p>
{currentStep === 1 && (
<div style={formSectionStyle}>
<h3>التفاصيل الشخصية</h3>
<label htmlFor="firstName">الاسم الأول:</label>
<input id="firstName" type="text" ref={this.firstNameRef} style={inputStyle} placeholder="مثال: جون" />
<label htmlFor="lastName">اسم العائلة:</label>
<input id="lastName" type="text" ref={this.lastNameRef} style={inputStyle} placeholder="مثال: دو" />
<button onClick={() => this.handleNextStep(this.emailRef)} style={buttonStyle}>التالي →</button>
</div>
)}
{currentStep === 2 && (
<div style={formSectionStyle}>
<h3>معلومات الاتصال</h3>
<label htmlFor="email">البريد الإلكتروني:</label>
<input id="email" type="email" ref={this.emailRef} style={inputStyle} placeholder="مثال: john.doe@example.com" />
<p>... حقول اتصال أخرى ...</p>
<button onClick={() => alert('تم إرسال النموذج!')} style={buttonStyle}>إرسال</button>
</div>
)}
<p><em>هذا التفاعل يعزز بشكل كبير إمكانية الوصول وتجربة المستخدم، خاصة للمستخدمين الذين يعتمدون على التنقل عبر لوحة المفاتيح أو التقنيات المساعدة عالميًا.</em></p>
</div>
);
}
}
يوضح هذا المثال نموذجًا عمليًا متعدد الخطوات حيث يتم استخدام createRef لإدارة التركيز برمجيًا. هذا يضمن رحلة مستخدم سلسة ويمكن الوصول إليها، وهو اعتبار حاسم للتطبيقات المستخدمة عبر سياقات لغوية وثقافية متنوعة. وبالمثل، بالنسبة لمشغلات الوسائط، تتيح لك المراجع بناء عناصر تحكم مخصصة (تشغيل، إيقاف مؤقت، مستوى الصوت، بحث) تتفاعل مباشرة مع واجهات برمجة التطبيقات الأصلية لعناصر <video> أو <audio> في HTML5، مما يوفر تجربة متسقة بغض النظر عن الإعدادات الافتراضية للمتصفح.
2. تشغيل الرسوم المتحركة الأمرية وتفاعلات Canvas
بينما تعتبر مكتبات الرسوم المتحركة التقريرية ممتازة للعديد من تأثيرات واجهة المستخدم، فإن بعض الرسوم المتحركة المتقدمة، خاصة تلك التي تستفيد من واجهة برمجة تطبيقات HTML5 Canvas أو WebGL أو التي تتطلب تحكمًا دقيقًا في خصائص العنصر التي من الأفضل إدارتها خارج دورة عرض React، تستفيد بشكل كبير من المراجع. على سبيل المثال، يتضمن إنشاء تصور بيانات في الوقت الفعلي أو لعبة على عنصر Canvas الرسم مباشرة على مخزن بكسل مؤقت، وهي عملية أمرية بطبيعتها.
import React from 'react';
class CanvasAnimator extends React.Component {
constructor(props) {
super(props);
this.canvasRef = React.createRef();
this.animationFrameId = null;
}
componentDidMount() {
this.startAnimation();
}
componentWillUnmount() {
this.stopAnimation();
}
startAnimation = () => {
const canvas = this.canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
let angle = 0;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 50;
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height); // مسح اللوحة
// رسم مربع دوار
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
ctx.fillStyle = '#6f42c1';
ctx.fillRect(-radius / 2, -radius / 2, radius, radius);
ctx.restore();
angle += 0.05; // زيادة الزاوية للدوران
this.animationFrameId = requestAnimationFrame(animate);
};
this.animationFrameId = requestAnimationFrame(animate);
};
stopAnimation = () => {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
};
render() {
return (
<div style={{ textAlign: 'center', margin: '30px auto', border: '1px solid #ced4da', padding: '20px', borderRadius: '8px', background: '#f8f9fa' }}>
<h3>رسوم متحركة أمرية على Canvas باستخدام createRef</h3>
<p>يتم التحكم في هذه الرسوم المتحركة على Canvas مباشرة باستخدام واجهات برمجة تطبيقات المتصفح عبر مرجع.</p>
<canvas ref={this.canvasRef} width="300" height="200" style={{ border: '1px solid #adb5bd', background: 'white' }}>
متصفحك لا يدعم وسم canvas في HTML5.
</canvas>
<p><em>هذا التحكم المباشر حيوي للرسومات عالية الأداء، أو الألعاب، أو تصورات البيانات المتخصصة المستخدمة في مختلف الصناعات على مستوى العالم.</em></p>
</div>
);
}
}
يوفر هذا المكون عنصر canvas ويستخدم مرجعًا للوصول المباشر إلى سياق العرض ثنائي الأبعاد الخاص به. ثم تقوم حلقة الرسوم المتحركة، التي تعمل بواسطة `requestAnimationFrame`، برسم وتحديث مربع دوار بشكل أمري. هذا النمط أساسي لبناء لوحات معلومات تفاعلية، أو أدوات تصميم عبر الإنترنت، أو حتى ألعاب بسيطة تتطلب عرضًا دقيقًا إطارًا بإطار، بغض النظر عن الموقع الجغرافي للمستخدم أو قدرات الجهاز.
3. التكامل مع مكتبات DOM من طرف ثالث: جسر سلس
أحد أكثر الأسباب إقناعًا لاستخدام المراجع هو دمج React مع مكتبات جافاسكريبت خارجية تتلاعب بـ DOM مباشرة. تعمل العديد من المكتبات القوية، خاصة القديمة منها أو تلك التي تركز على مهام عرض محددة (مثل الرسوم البيانية، أو الخرائط، أو تحرير النصوص المنسقة)، عن طريق أخذ عنصر DOM كهدف ثم إدارة محتواه بأنفسهم. React، في وضعه التقريري، سيتعارض مع هذه المكتبات بمحاولة التحكم في نفس شجرة DOM الفرعية. تمنع المراجع هذا التعارض من خلال توفير "حاوية" مخصصة للمكتبة الخارجية.
import React from 'react';
import * as d3 from 'd3'; // بافتراض أن D3.js مثبت ومستورد
class D3BarChart extends React.Component {
constructor(props) {
super(props);
this.chartContainerRef = React.createRef();
}
// عند تركيب المكون، ارسم الرسم البياني
componentDidMount() {
this.drawChart();
}
// عند تحديث المكون (مثل تغيير props.data)، قم بتحديث الرسم البياني
componentDidUpdate(prevProps) {
if (prevProps.data !== this.props.data) {
this.drawChart();
}
}
// عند إلغاء تركيب المكون، قم بتنظيف عناصر D3 لمنع تسرب الذاكرة
componentWillUnmount() {
d3.select(this.chartContainerRef.current).selectAll('*').remove();
}
drawChart = () => {
const data = this.props.data || [40, 80, 20, 100, 60, 90]; // بيانات افتراضية
const node = this.chartContainerRef.current;
if (!node) return; // تأكد من أن المرجع متاح
// مسح أي عناصر رسم بياني سابقة رسمتها D3
d3.select(node).selectAll('*').remove();
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const width = 460 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const svg = d3.select(node)
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// إعداد المقاييس
const x = d3.scaleBand()
.range([0, width])
.padding(0.1);
const y = d3.scaleLinear()
.range([height, 0]);
x.domain(data.map((d, i) => i)); // استخدام الفهرس كمجال للتبسيط
y.domain([0, d3.max(data)]);
// إضافة الأشرطة
svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i) => x(i))
.attr('width', x.bandwidth())
.attr('y', d => y(d))
.attr('height', d => height - y(d))
.attr('fill', '#17a2b8');
// إضافة المحور السيني
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x));
// إضافة المحور الصادي
svg.append('g')
.call(d3.axisLeft(y));
};
render() {
return (
<div style={{ textAlign: 'center', margin: '30px auto', border: '1px solid #00a0b2', padding: '20px', borderRadius: '8px', background: '#e0f7fa' }}>
<h3>تكامل رسوم D3.js البيانية مع React createRef</h3>
<p>يتم عرض تصور البيانات هذا بواسطة D3.js داخل حاوية تديرها React.</p>
<div ref={this.chartContainerRef} /> // ستقوم D3.js بالعرض داخل هذا الـ div
<p><em>يعد دمج مثل هذه المكتبات المتخصصة أمرًا حاسمًا للتطبيقات كثيفة البيانات، مما يوفر أدوات تحليلية قوية للمستخدمين عبر مختلف الصناعات والمناطق.</em></p>
</div>
);
}
}
يعرض هذا المثال الشامل تكامل رسم بياني شريطي من D3.js داخل مكون صنف في React. يوفر chartContainerRef لـ D3.js عقدة DOM المحددة التي يحتاجها لأداء عرضه. تتولى React دورة حياة الحاوية <div>، بينما تدير D3.js محتواها الداخلي. تعتبر أساليب `componentDidUpdate` و `componentWillUnmount` حيوية لتحديث الرسم البياني عند تغيير البيانات وللقيام بالتنظيف اللازم، مما يمنع تسرب الذاكرة ويضمن تجربة سريعة الاستجابة. هذا النمط قابل للتطبيق عالميًا، مما يسمح للمطورين بالاستفادة من أفضل ما في نموذج مكونات React والمكتبات المتخصصة عالية الأداء للتصورات البيانية للوحات المعلومات والتحليلات العالمية.
4. قياس أبعاد العنصر أو موضعه للتخطيطات الديناميكية
بالنسبة للتخطيطات الديناميكية أو سريعة الاستجابة للغاية، أو لتنفيذ ميزات مثل القوائم الافتراضية التي تعرض فقط العناصر المرئية، فإن معرفة الأبعاد والموضع الدقيق للعناصر أمر حاسم. تتيح لك المراجع الوصول إلى أسلوب getBoundingClientRect()، الذي يوفر هذه المعلومات الهامة مباشرة من DOM.
import React from 'react';
class ElementDimensionLogger extends React.Component {
constructor(props) {
super(props);
this.measurableDivRef = React.createRef();
this.state = {
width: 0,
height: 0,
top: 0,
left: 0,
message: 'انقر على الزر للقياس!'
};
}
componentDidMount() {
// القياس الأولي غالبًا ما يكون مفيدًا، ولكن يمكن أيضًا تشغيله بواسطة إجراء المستخدم
this.measureElement();
// للتخطيطات الديناميكية، قد تستمع إلى أحداث تغيير حجم النافذة
window.addEventListener('resize', this.measureElement);
}
componentWillUnmount() {
window.removeEventListener('resize', this.measureElement);
}
measureElement = () => {
if (this.measurableDivRef.current) {
const rect = this.measurableDivRef.current.getBoundingClientRect();
this.setState({
width: Math.round(rect.width),
height: Math.round(rect.height),
top: Math.round(rect.top),
left: Math.round(rect.left),
message: 'تم تحديث الأبعاد.'
});
} else {
this.setState({ message: 'العنصر لم يتم عرضه بعد.' });
}
};
render() {
const { width, height, top, left, message } = this.state;
const boxStyle = {
width: '70%',
minHeight: '150px',
border: '3px solid #ffc107',
margin: '25px auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
background: '#fff3cd',
borderRadius: '8px',
textAlign: 'center'
};
return (
<div style={{ maxWidth: '700px', margin: '30px auto', padding: '25px', boxShadow: '0 4px 12px rgba(0,0,0,0.08)', borderRadius: '10px', background: 'white' }}>
<h3>قياس أبعاد العنصر باستخدام createRef</h3>
<p>هذا المثال يجلب ويعرض ديناميكيًا حجم وموضع عنصر مستهدف.</p>
<div ref={this.measurableDivRef} style={boxStyle}>
<p><strong>أنا العنصر الذي يتم قياسه.</strong></p>
<p>غير حجم نافذة متصفحك لترى القياسات تتغير عند التحديث / التشغيل اليدوي.</p>
</div>
<button
onClick={this.measureElement}
style={{ padding: '10px 20px', background: '#6c757d', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', marginBottom: '15px' }}
>
قس الآن
</button>
<div style={{ background: '#f0f0f0', padding: '15px', borderRadius: '6px' }}>
<p><strong>الأبعاد الحية:</strong></p>
<ul style={{ listStyleType: 'none', padding: 0, textAlign: 'left', margin: '0 auto', maxWidth: '300px' }}>
<li>العرض: <b>{width}px</b></li>
<li>الارتفاع: <b>{height}px</b></li>
<li>الموضع العلوي (منطقة العرض): <b>{top}px</b></li>
<li>الموضع الأيسر (منطقة العرض): <b>{left}px</b></li>
</ul>
<p><em>يعد قياس العناصر بدقة أمرًا حاسمًا للتصميمات سريعة الاستجابة وتحسين الأداء على الأجهزة المتنوعة على مستوى العالم.</em></p>
</div>
</div>
);
}
}
يستخدم هذا المكون createRef للحصول على getBoundingClientRect() لعنصر div، مما يوفر أبعاده وموضعه في الوقت الفعلي. هذه المعلومات لا تقدر بثمن لتنفيذ تعديلات تخطيط معقدة، أو تحديد الرؤية في قائمة تمرير افتراضية، أو حتى لضمان وجود العناصر داخل منطقة عرض محددة. بالنسبة لجمهور عالمي، حيث تختلف أحجام الشاشات والدقة وبيئات المتصفح بشكل كبير، يعد التحكم الدقيق في التخطيط بناءً على قياسات DOM الفعلية عاملاً رئيسيًا في تقديم تجربة مستخدم متسقة وعالية الجودة.
أفضل الممارسات والمحاذير لاستخدام `createRef`
بينما يوفر createRef تحكمًا أمريًا قويًا، فإن إساءة استخدامه يمكن أن تؤدي إلى كود يصعب إدارته وتصحيح أخطائه. يعد الالتزام بأفضل الممارسات أمرًا ضروريًا لتسخير قوته بمسؤولية.
1. إعطاء الأولوية للنهج التقريرية: القاعدة الذهبية
تذكر دائمًا أن المراجع هي "مخرج طوارئ"، وليست الطريقة الأساسية للتفاعل في React. قبل اللجوء إلى مرجع، اسأل نفسك: هل يمكن تحقيق ذلك بالحالة والخصائص؟ إذا كانت الإجابة بنعم، فهذا دائمًا هو النهج الأفضل والأكثر توافقًا مع "أسلوب React". على سبيل المثال، إذا كنت تريد تغيير قيمة إدخال، فاستخدم المكونات المتحكم فيها مع الحالة، وليس مرجعًا لتعيين inputRef.current.value مباشرة.
2. المراجع للتفاعلات الأمرية، وليس لإدارة الحالة
المراجع هي الأنسب للمهام التي تتضمن إجراءات مباشرة وأمرية على عناصر DOM أو نسخ المكونات. إنها أوامر: "ركز على هذا الإدخال"، "شغل هذا الفيديو"، "مرر إلى هذا القسم". ليست مخصصة لتغيير واجهة المستخدم التقريرية للمكون بناءً على الحالة. يمكن أن يؤدي التلاعب المباشر بنمط أو محتوى عنصر عبر مرجع عندما يمكن التحكم فيه بواسطة الخصائص أو الحالة إلى عدم تزامن DOM الافتراضي لـ React مع DOM الفعلي، مما يسبب سلوكًا غير متوقع ومشكلات في العرض.
3. المراجع والمكونات الوظيفية: تبنى `useRef` و `forwardRef`
لتطوير React الحديث داخل المكونات الوظيفية، فإن React.createRef() ليس الأداة التي ستستخدمها. بدلاً من ذلك، ستعتمد على خطاف useRef. يوفر خطاف useRef كائن مرجع قابل للتغيير مشابه لـ createRef، يمكن استخدام خاصية .current الخاصة به لنفس التفاعلات الأمرية. يحتفظ بقيمته عبر عمليات إعادة عرض المكون دون التسبب في إعادة عرض بنفسه، مما يجعله مثاليًا للاحتفاظ بمرجع لعقدة DOM أو أي قيمة قابلة للتغيير تحتاج إلى الاستمرار عبر العروض.
import React, { useRef, useEffect } from 'react';
function FunctionalComponentWithRef() {
const myInputRef = useRef(null); // تهيئة بـ null
useEffect(() => {
// يعمل هذا بعد تركيب المكون
if (myInputRef.current) {
myInputRef.current.focus();
console.log('Functional component input focused!');
}
}, []); // مصفوفة التبعية الفارغة تضمن تشغيله مرة واحدة فقط عند التركيب
const handleLogValue = () => {
if (myInputRef.current) {
alert(`قيمة الإدخال: ${myInputRef.current.value}`);
}
};
return (
<div style={{ margin: '20px', padding: '20px', border: '1px solid #009688', borderRadius: '8px', background: '#e0f2f1' }}>
<h3>استخدام useRef في مكون وظيفي</h3>
<label htmlFor="funcInput">اكتب شيئًا:</label><br />
<input id="funcInput" type="text" ref={myInputRef} placeholder="يتم التركيز علي تلقائيًا!" style={{ padding: '8px', margin: '10px 0', borderRadius: '4px', border: '1px solid #ccc' }} /><br />
<button onClick={handleLogValue} style={{ padding: '10px 15px', background: '#009688', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}>
تسجيل قيمة الإدخال
</button>
<p><em>بالنسبة للمشاريع الجديدة، يعد `useRef` هو الخيار الاصطلاحي للمراجع في المكونات الوظيفية.</em></p>
</div>
);
}
إذا كنت بحاجة إلى مكون أصلي للحصول على مرجع لعنصر DOM داخل مكون وظيفي ابن، فإن React.forwardRef هو الحل. إنه مكون عالي الرتبة يسمح لك بـ "إعادة توجيه" مرجع من أصل إلى أحد عناصر DOM لأبنائه، مع الحفاظ على تغليف المكون الوظيفي مع تمكين الوصول الأمري عند الحاجة.
import React, { useRef, useEffect } from 'react';
// مكون وظيفي يعيد توجيه مرجع صراحة إلى عنصر الإدخال الأصلي الخاص به
const ForwardedInput = React.forwardRef((props, ref) => (
<input type="text" ref={ref} className="forwarded-input" placeholder={props.placeholder} style={{ padding: '10px', margin: '8px 0', border: '1px solid #ccc', borderRadius: '4px', width: '100%' }} />
));
class ParentComponentUsingForwardRef extends React.Component {
constructor(props) {
super(props);
this.parentInputRef = React.createRef();
}
componentDidMount() {
if (this.parentInputRef.current) {
this.parentInputRef.current.focus();
console.log('Input inside functional component focused from parent (class component) via forwarded ref!');
}
}
render() {
return (
<div style={{ margin: '20px', padding: '20px', border: '1px solid #6f42c1', borderRadius: '8px', background: '#f5eef9' }}>
<h3>مثال على إعادة توجيه المرجع باستخدام createRef (مكون أصلي صنف)</h3>
<label>أدخل التفاصيل:</label>
<ForwardedInput ref={this.parentInputRef} placeholder="هذا الإدخال داخل مكون وظيفي" />
<p><em>هذا النمط حاسم لإنشاء مكتبات مكونات قابلة لإعادة الاستخدام تحتاج إلى كشف الوصول المباشر إلى DOM.</em></p>
</div>
);
}
}
يوضح هذا كيف يمكن لمكون صنف يستخدم createRef التفاعل بفعالية مع عنصر DOM متداخل داخل مكون وظيفي من خلال الاستفادة من forwardRef. هذا يجعل المكونات الوظيفية قادرة بنفس القدر على المشاركة في التفاعلات الأمرية عند الحاجة، مما يضمن أن قواعد الكود الحديثة في React لا يزال بإمكانها الاستفادة من المراجع.
4. متى لا تستخدم المراجع: الحفاظ على سلامة React
- للتحكم في حالة المكون الابن: لا تستخدم أبدًا مرجعًا لقراءة أو تحديث حالة مكون ابن مباشرة. هذا يتجاوز إدارة الحالة في React، مما يجعل تطبيقك غير قابل للتنبؤ. بدلاً من ذلك، قم بتمرير الحالة كخصائص، واستخدم الاستدعاءات المرتجعة للسماح للأبناء بطلب تغييرات الحالة من الآباء.
- كبديل للخصائص: بينما يمكنك استدعاء أساليب على مكون صنف ابن عبر مرجع، فكر فيما إذا كان تمرير معالج حدث كخاصية إلى الابن سيحقق نفس الهدف بطريقة أكثر توافقًا مع "أسلوب React". تعزز الخصائص تدفق البيانات الواضح وتجعل تفاعلات المكونات شفافة.
-
للتلاعب البسيط بـ DOM الذي يمكن لـ React التعامل معه: إذا كنت تريد تغيير نص عنصر، أو نمطه، أو إضافة/إزالة صنف بناءً على الحالة، فافعل ذلك بشكل تقريري. على سبيل المثال، لتبديل صنف
active، قم بتطبيقه شرطيًا في JSX:<div className={isActive ? 'active' : ''}>، بدلاً منdivRef.current.classList.add('active').
5. اعتبارات الأداء والوصول العالمي
بينما يعتبر createRef نفسه عالي الأداء، فإن العمليات التي يتم إجراؤها باستخدام current يمكن أن يكون لها آثار كبيرة على الأداء. بالنسبة للمستخدمين على الأجهزة ذات المواصفات المنخفضة أو اتصالات الشبكة الأبطأ (وهو أمر شائع في أجزاء كثيرة من العالم)، يمكن أن يؤدي التلاعب غير الفعال بـ DOM إلى تقطع، وواجهات مستخدم غير مستجيبة، وتجربة مستخدم سيئة. عند استخدام المراجع لمهام مثل الرسوم المتحركة، أو حسابات التخطيط المعقدة، أو دمج مكتبات طرف ثالث ثقيلة:
-
تأخير/تنظيم الأحداث: إذا كنت تستخدم المراجع لقياس الأبعاد في أحداث
window.resizeأوscroll، فتأكد من أن هذه المعالجات مؤجلة أو منظمة لمنع الاستدعاءات المفرطة للوظائف وقراءات DOM. -
تجميع قراءات/كتابات DOM: تجنب تداخل عمليات قراءة DOM (مثل
getBoundingClientRect()) مع عمليات كتابة DOM (مثل تعيين الأنماط). يمكن أن يؤدي هذا إلى تعثر التخطيط. يمكن أن تساعد أدوات مثلfastdomفي إدارة هذا بكفاءة. -
تأجيل العمليات غير الحرجة: استخدم
requestAnimationFrameللرسوم المتحركة وsetTimeout(..., 0)أوrequestIdleCallbackللتلاعب الأقل أهمية بـ DOM لضمان عدم حظرها للخيط الرئيسي والتأثير على الاستجابة. - اختر بحكمة: في بعض الأحيان، يمكن أن يكون أداء مكتبة طرف ثالث عنق زجاجة. قم بتقييم البدائل أو فكر في التحميل الكسول لمثل هذه المكونات للمستخدمين على اتصالات أبطأ، مما يضمن بقاء تجربة أساسية عالية الأداء على مستوى العالم.
`createRef` مقابل المراجع المرتجعة مقابل `useRef`: مقارنة تفصيلية
قدمت React طرقًا مختلفة للتعامل مع المراجع طوال تطورها. يعد فهم الفروق الدقيقة لكل منها أمرًا أساسيًا لاختيار الطريقة الأنسب لسياقك المحدد.
1. `React.createRef()` (المكونات الصنفية - حديث)
-
الآلية: ينشئ كائن مرجع (
{ current: null }) في مُنشئ نسخة المكون. تقوم React بتعيين عنصر DOM أو نسخة المكون إلى خاصية.currentبعد التركيب. - الاستخدام الأساسي: حصريًا داخل المكونات الصنفية. يتم تهيئته مرة واحدة لكل نسخة مكون.
-
تعبئة المرجع: يتم تعيين
.currentإلى العنصر/النسخة بعد تركيب المكون، ويعاد تعيينه إلىnullعند إلغاء التركيب. - الأفضل لـ: جميع متطلبات المراجع القياسية في المكونات الصنفية حيث تحتاج إلى الإشارة إلى عنصر DOM أو نسخة مكون صنف ابن.
- المزايا: بنية واضحة ومباشرة موجهة للكائنات. لا توجد مخاوف بشأن إعادة إنشاء الوظيفة المضمنة مما يسبب استدعاءات إضافية (كما يمكن أن يحدث مع المراجع المرتجعة).
- العيوب: غير قابل للاستخدام مع المكونات الوظيفية. إذا لم يتم تهيئته في المُنشئ (على سبيل المثال، في العرض)، فقد يتم إنشاء كائن مرجع جديد في كل عرض، مما يؤدي إلى مشكلات أداء محتملة أو قيم مرجع غير صحيحة. يتطلب تذكر التعيين إلى خاصية نسخة.
2. المراجع المرتجعة (المكونات الصنفية والوظيفية - مرنة/قديمة)
-
الآلية: تقوم بتمرير وظيفة مباشرة إلى خاصية
ref. تستدعي React هذه الوظيفة مع عنصر DOM المركب أو نسخة المكون، ولاحقًا معnullعند إلغاء تركيبه. -
الاستخدام الأساسي: يمكن استخدامها في كل من المكونات الصنفية والوظيفية. في المكونات الصنفية، عادة ما تكون الاستدعاءات المرتجعة مرتبطة بـ
thisأو معرفة كخاصية صنف دالة سهمية. في المكونات الوظيفية، غالبًا ما يتم تعريفها مضمنة أو باستخدام memoized. -
تعبئة المرجع: يتم استدعاء وظيفة الاستدعاء المرتجع بواسطة React مباشرة. أنت مسؤول عن تخزين المرجع (على سبيل المثال،
this.myInput = element;). -
الأفضل لـ: السيناريوهات التي تتطلب تحكمًا أكثر دقة في وقت تعيين المراجع وإلغاء تعيينها، أو للأنماط المتقدمة مثل قوائم المراجع الديناميكية. كانت الطريقة الأساسية لإدارة المراجع قبل
createRefوuseRef. - المزايا: توفر أقصى قدر من المرونة. تمنحك وصولاً فوريًا إلى المرجع عندما يكون متاحًا (داخل وظيفة الاستدعاء المرتجع). يمكن استخدامها لتخزين المراجع في مصفوفة أو خريطة للمجموعات الديناميكية من العناصر.
-
العيوب: إذا تم تعريف الاستدعاء المرتجع مضمنًا داخل أسلوب
render(على سبيل المثال،ref={el => this.myRef = el})، فسيتم استدعاؤه مرتين أثناء التحديثات (مرة معnull، ثم مع العنصر)، مما قد يسبب مشكلات في الأداء أو آثارًا جانبية غير متوقعة إذا لم يتم التعامل معها بعناية (على سبيل المثال، عن طريق جعل الاستدعاء المرتجع أسلوب صنف أو استخدامuseCallbackفي المكونات الوظيفية).
class CallbackRefDetailedExample extends React.Component {
constructor(props) {
super(props);
this.inputElement = null;
}
// سيتم استدعاء هذا الأسلوب بواسطة React لتعيين المرجع
setInputElementRef = element => {
if (element) {
console.log('Ref element is:', element);
}
this.inputElement = element; // تخزين عنصر DOM الفعلي
};
componentDidMount() {
if (this.inputElement) {
this.inputElement.focus();
}
}
render() {
return (
<div>
<label>إدخال مرجع مرتجع:</label>
<input type="text" ref={this.setInputElementRef} />
</div>
);
}
}
3. خطاف `useRef` (المكونات الوظيفية - حديث)
-
الآلية: خطاف React يعيد كائن مرجع قابل للتغيير (
{ current: initialValue }). يستمر الكائن المرتجع طوال عمر المكون الوظيفي. - الاستخدام الأساسي: حصريًا داخل المكونات الوظيفية.
-
تعبئة المرجع: على غرار
createRef، تقوم React بتعيين عنصر DOM أو نسخة المكون (إذا تم إعادة توجيهها) إلى خاصية.currentبعد التركيب وتعيينها إلىnullعند إلغاء التركيب. يمكن أيضًا تحديث قيمة.currentيدويًا. - الأفضل لـ: جميع عمليات إدارة المراجع في المكونات الوظيفية. مفيد أيضًا للاحتفاظ بأي قيمة قابلة للتغيير تحتاج إلى الاستمرار عبر العروض دون تشغيل إعادة عرض (مثل معرفات المؤقت، القيم السابقة).
- المزايا: بسيط، اصطلاحي للخطافات. يستمر كائن المرجع عبر العروض، متجنبًا مشكلات إعادة الإنشاء. يمكن تخزين أي قيمة قابلة للتغيير، وليس فقط عقد DOM.
-
العيوب: يعمل فقط داخل المكونات الوظيفية. يتطلب
useEffectصريحًا للتفاعلات المرجعية المتعلقة بدورة الحياة (مثل التركيز عند التركيب).
باختصار:
-
إذا كنت تكتب مكونًا صنفيًا وتحتاج إلى مرجع، فإن
React.createRef()هو الخيار الموصى به والأكثر وضوحًا. -
إذا كنت تكتب مكونًا وظيفيًا وتحتاج إلى مرجع، فإن خطاف
useRefهو الحل الحديث والاصطلاحي. - لا تزال المراجع المرتجعة صالحة ولكنها بشكل عام أكثر تفصيلاً وعرضة لمشكلات دقيقة إذا لم يتم تنفيذها بعناية. وهي مفيدة للسيناريوهات المتقدمة أو عند العمل مع قواعد كود أقدم أو سياقات لا تتوفر فيها الخطافات.
-
لتمرير المراجع عبر المكونات (خاصة الوظيفية)، فإن
React.forwardRef()ضروري، وغالبًا ما يستخدم بالاقتران معcreateRefأوuseRefفي المكون الأصل.
الاعتبارات العالمية وإمكانية الوصول المتقدمة مع المراجع
بينما غالبًا ما تتم مناقشته في فراغ تقني، فإن استخدام المراجع في سياق تطبيق ذي عقلية عالمية يحمل آثارًا مهمة، لا سيما فيما يتعلق بالأداء وإمكانية الوصول للمستخدمين المتنوعين.
1. تحسين الأداء للأجهزة والشبكات المتنوعة
إن تأثير createRef نفسه على حجم الحزمة ضئيل، لأنه جزء صغير من نواة React. ومع ذلك، يمكن أن يكون للعمليات التي تجريها باستخدام خاصية current آثار كبيرة على الأداء. بالنسبة للمستخدمين على الأجهزة ذات المواصفات المنخفضة أو اتصالات الشبكة الأبطأ (وهو أمر شائع في أجزاء كثيرة من العالم)، يمكن أن يؤدي التلاعب غير الفعال بـ DOM إلى تقطع، وواجهات مستخدم غير مستجيبة، وتجربة مستخدم سيئة. عند استخدام المراجع لمهام مثل الرسوم المتحركة، أو حسابات التخطيط المعقدة، أو دمج مكتبات طرف ثالث ثقيلة:
-
تأخير/تنظيم الأحداث: إذا كنت تستخدم المراجع لقياس الأبعاد في أحداث
window.resizeأوscroll، فتأكد من أن هذه المعالجات مؤجلة أو منظمة لمنع الاستدعاءات المفرطة للوظائف وقراءات DOM. -
تجميع قراءات/كتابات DOM: تجنب تداخل عمليات قراءة DOM (مثل
getBoundingClientRect()) مع عمليات كتابة DOM (مثل تعيين الأنماط). يمكن أن يؤدي هذا إلى تعثر التخطيط. يمكن أن تساعد أدوات مثلfastdomفي إدارة هذا بكفاءة. -
تأجيل العمليات غير الحرجة: استخدم
requestAnimationFrameللرسوم المتحركة وsetTimeout(..., 0)أوrequestIdleCallbackللتلاعب الأقل أهمية بـ DOM لضمان عدم حظرها للخيط الرئيسي والتأثير على الاستجابة. - اختر بحكمة: في بعض الأحيان، يمكن أن يكون أداء مكتبة طرف ثالث عنق زجاجة. قم بتقييم البدائل أو فكر في التحميل الكسول لمثل هذه المكونات للمستخدمين على اتصالات أبطأ، مما يضمن بقاء تجربة أساسية عالية الأداء على مستوى العالم.
2. تعزيز إمكانية الوصول (سمات ARIA والتنقل عبر لوحة المفاتيح)
تعتبر المراجع مفيدة في بناء تطبيقات ويب يمكن الوصول إليها بشكل كبير، خاصة عند إنشاء مكونات واجهة مستخدم مخصصة لا تحتوي على مكافئات متصفح أصلية أو عند تجاوز السلوكيات الافتراضية. بالنسبة لجمهور عالمي، فإن الالتزام بإرشادات الوصول إلى محتوى الويب (WCAG) ليس مجرد ممارسة جيدة، بل غالبًا ما يكون مطلبًا قانونيًا. تمكّن المراجع من:
- إدارة التركيز البرمجي: كما رأينا مع حقول الإدخال، تتيح لك المراجع تعيين التركيز، وهو أمر حاسم لمستخدمي لوحة المفاتيح والتنقل بقارئ الشاشة. وهذا يشمل إدارة التركيز داخل النوافذ المنبثقة، أو القوائم المنسدلة، أو الأدوات التفاعلية.
-
سمات ARIA الديناميكية: يمكنك استخدام المراجع لإضافة أو تحديث سمات ARIA (تطبيقات الإنترنت الغنية التي يمكن الوصول إليها) ديناميكيًا (مثل
aria-expanded،aria-controls،aria-live) على عناصر DOM. يوفر هذا معلومات دلالية للتقنيات المساعدة التي قد لا يمكن استنتاجها من واجهة المستخدم المرئية وحدها.class CollapsibleSection extends React.Component {
constructor(props) {
super(props);
this.buttonRef = React.createRef();
this.state = { isExpanded: false };
}
toggleExpanded = () => {
this.setState(prevState => ({ isExpanded: !prevState.isExpanded }), () => {
if (this.buttonRef.current) {
// تحديث سمة ARIA ديناميكيًا بناءً على الحالة
this.buttonRef.current.setAttribute('aria-expanded', this.state.isExpanded);
}
});
};
componentDidMount() {
if (this.buttonRef.current) {
this.buttonRef.current.setAttribute('aria-controls', `section-${this.props.id}`);
this.buttonRef.current.setAttribute('aria-expanded', this.state.isExpanded);
}
}
render() {
const { id, title, children } = this.props;
const { isExpanded } = this.state;
return (
<div style={{ margin: '20px auto', maxWidth: '600px', border: '1px solid #0056b3', borderRadius: '8px', background: '#e7f0fa', overflow: 'hidden' }}>
<h4>
<button
ref={this.buttonRef} // مرجع للزر لسمات ARIA
onClick={this.toggleExpanded}
style={{ background: 'none', border: 'none', padding: '15px 20px', width: '100%', textAlign: 'left', cursor: 'pointer', fontSize: '1.2em', color: '#0056b3', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
id={`section-header-${id}`}
>
{title} <span>▼</span>
</button>
</h4>
{isExpanded && (
<div id={`section-${id}`} role="region" aria-labelledby={`section-header-${id}`} style={{ padding: '0 20px 20px', borderTop: '1px solid #a7d9f7' }}>
{children}
</div>
)}
</div>
);
}
} - التحكم في التفاعل عبر لوحة المفاتيح: بالنسبة للقوائم المنسدلة المخصصة، أو أشرطة التمرير، أو العناصر التفاعلية الأخرى، قد تحتاج إلى تنفيذ معالجات أحداث لوحة المفاتيح المحددة (مثل مفاتيح الأسهم للتنقل داخل قائمة). توفر المراجع الوصول إلى عنصر DOM المستهدف حيث يمكن إرفاق وإدارة مستمعي الأحداث هؤلاء.
من خلال تطبيق المراجع بعناية، يمكن للمطورين ضمان أن تطبيقاتهم قابلة للاستخدام وشاملة للأشخاص ذوي الإعاقة في جميع أنحاء العالم، مما يوسع بشكل كبير من وصولهم وتأثيرهم العالمي.
3. التدويل (I18n) والتفاعلات المترجمة
عند العمل مع التدويل (i18n)، يمكن للمراجع أن تلعب دورًا دقيقًا ولكن مهمًا. على سبيل المثال، في اللغات التي تستخدم نصًا من اليمين إلى اليسار (RTL) (مثل العربية أو العبرية أو الفارسية)، يمكن أن يختلف ترتيب التنقل الطبيعي واتجاه التمرير عن اللغات من اليسار إلى اليمين (LTR). إذا كنت تدير التركيز أو التمرير برمجيًا باستخدام المراجع، فمن الضروري التأكد من أن منطقك يحترم اتجاه نص المستند أو العنصر (سمة dir).
- إدارة التركيز المدركة لـ RTL: بينما تتعامل المتصفحات بشكل عام مع ترتيب التنقل الافتراضي بشكل صحيح لـ RTL، إذا كنت تنفذ مصائد تركيز مخصصة أو تركيزًا تسلسليًا، فاختبر منطقك القائم على المراجع جيدًا في بيئات RTL لضمان تجربة متسقة وبديهية.
-
قياس التخطيط في RTL: عند استخدام
getBoundingClientRect()عبر مرجع، كن على دراية بأن خصائصleftوrightنسبية لمنطقة العرض. لحسابات التخطيط التي تعتمد على البداية/النهاية المرئية، ضع في اعتباركdocument.dirأو النمط المحسوب للعنصر لضبط منطقك لتخطيطات RTL. - تكامل مكتبات الطرف الثالث: تأكد من أن أي مكتبات طرف ثالث مدمجة عبر المراجع (مثل مكتبات الرسوم البيانية) هي نفسها مدركة لـ i18n وتتعامل مع تخطيطات RTL بشكل صحيح إذا كان تطبيقك يدعمها. غالبًا ما تقع مسؤولية ضمان ذلك على عاتق المطور الذي يدمج المكتبة في مكون React.
الخاتمة: إتقان التحكم الأمري باستخدام `createRef` للتطبيقات العالمية
إن React.createRef() هو أكثر من مجرد "مخرج طوارئ" في React؛ إنه أداة حيوية تسد الفجوة بين نموذج React التقريري القوي والواقع الأمري لتفاعلات DOM في المتصفح. بينما تم استبدال دوره إلى حد كبير في المكونات الوظيفية الأحدث بخطاف useRef، يظل createRef هو الطريقة القياسية والأكثر اصطلاحية لإدارة المراجع داخل المكونات الصنفية، والتي لا تزال تشكل جزءًا كبيرًا من العديد من تطبيقات المؤسسات في جميع أنحاء العالم.
من خلال فهم إنشائه، وإرفاقه، والدور الحاسم لخاصية .current، يمكن للمطورين التعامل بثقة مع تحديات مثل إدارة التركيز البرمجي، والتحكم المباشر في الوسائط، والتكامل السلس مع مكتبات الطرف الثالث المتنوعة (من رسوم D3.js البيانية إلى محررات النصوص المنسقة المخصصة)، وقياس أبعاد العناصر بدقة. هذه القدرات ليست مجرد مآثر تقنية؛ إنها أساسية لبناء تطبيقات ذات أداء عالٍ، ويمكن الوصول إليها، وسهلة الاستخدام عبر طيف واسع من المستخدمين والأجهزة والسياقات الثقافية العالمية.
تذكر أن تستخدم هذه القوة بحكمة. فضل دائمًا نظام الحالة والخصائص التقريري في React أولاً. عندما تكون السيطرة الأمرية ضرورية حقًا، يوفر createRef (للمكونات الصنفية) أو useRef (للمكونات الوظيفية) آلية قوية ومحددة جيدًا لتحقيق ذلك. يمكّنك إتقان المراجع من التعامل مع الحالات الهامشية وتعقيدات تطوير الويب الحديث، مما يضمن أن تطبيقات React الخاصة بك يمكن أن تقدم تجارب مستخدم استثنائية في أي مكان في العالم، مع الحفاظ على الفوائد الأساسية لمعمارية React الأنيقة القائمة على المكونات.
لمزيد من التعلم والاستكشاف
- وثائق React الرسمية حول المراجع: للحصول على أحدث المعلومات مباشرة من المصدر، راجع <em>https://react.dev/learn/manipulating-the-dom-with-refs</em>
- فهم خطاف `useRef` في React: للتعمق أكثر في المكافئ في المكونات الوظيفية، استكشف <em>https://react.dev/reference/react/useRef</em>
- إعادة توجيه المرجع باستخدام `forwardRef`: تعلم كيفية تمرير المراجع عبر المكونات بفعالية: <em>https://react.dev/reference/react/forwardRef</em>
- إرشادات الوصول إلى محتوى الويب (WCAG): ضرورية لتطوير الويب العالمي: <em>https://www.w3.org/WAI/WCAG22/quickref/</em>
- تحسين أداء React: أفضل الممارسات للتطبيقات عالية الأداء: <em>https://react.dev/learn/optimizing-performance</em>